﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Liquid.Collections {
	/// <summary>
	/// A base class for buffers that have the property
	/// that insertion and deletion are cheap, but only
	/// when done at the current position.
	/// </summary>
	/// <typeparam name="T">The type of items in the buffer</typeparam>
	public abstract class BufferBase<T> : IBuffer<T> {
		/// <summary>
		/// Initializes a new instance of the <see cref="BufferBase&lt;T&gt;"/> class, using the default comparer.
		/// </summary>
		public BufferBase()
			: this(EqualityComparer<T>.Default) { }

		/// <summary>
		/// Initializes a new instance of the <see cref="BufferBase&lt;T&gt;"/> class, with the specified comparer.
		/// </summary>
		/// <param name="comparer">The comparer to use.</param>
		public BufferBase(IEqualityComparer<T> comparer) {
			this.Comparer = comparer;
		}

		/// <summary>
		/// Gets the comparer that the collection should use to compare items.
		/// </summary>
		/// <value>The comparer.</value>
		protected IEqualityComparer<T> Comparer { get; private set; }

		/// <summary>
		/// Gets or sets the current position.
		/// </summary>
		/// <value>The position.</value>
		public abstract int Position { get; set; }
		/// <summary>
		/// Gets or sets the capacity of the buffer.
		/// </summary>
		/// <value>The capacity.</value>
		public abstract int Capacity { get; set; }

		/// <summary>
		/// Inserts an item into the buffer at the current position.
		/// </summary>
		/// <param name="item">The item to insert.</param>
		public virtual void Insert(T item) {
			InsertRange(new T[] { item });
		}
		/// <summary>
		/// Inserts a range of items into the buffer at the current position.
		/// </summary>
		/// <param name="array">The items to insert.</param>
		public virtual void InsertRange(T[] array) {
			InsertRange((IList<T>)array);
		}
		/// <summary>
		/// Inserts a range of items into the buffer at the current position.
		/// </summary>
		/// <param name="list">The items to insert.</param>
		public virtual void InsertRange(IList<T> list) {
			InsertRange((IEnumerable<T>)list);
		}
		/// <summary>
		/// Inserts a range of items into the buffer at the current position.
		/// </summary>
		/// <param name="from">The items to insert.</param>
		public abstract void InsertRange(IEnumerable<T> from);

		/// <summary>
		/// Deletes the item after the current position from the buffer.
		/// </summary>
		public virtual void DeleteNext() {
			DeleteNext(1);
		}
		/// <summary>
		/// Deletes a number of items from the buffer, after the current position.
		/// </summary>
		/// <param name="count">The number of items to delete.</param>
		public abstract void DeleteNext(int count);

		/// <summary>
		/// Deletes the item before the current position from the buffer.
		/// </summary>
		public virtual void DeletePrevious() {
			DeletePrevious(1);
		}
		/// <summary>
		/// Deletes a number of items from the buffer, before the current position.
		/// </summary>
		/// <param name="count">The number of items to delete.</param>
		public abstract void DeletePrevious(int count);

		/// <summary>
		/// Copies items from the buffer into an array.
		/// </summary>
		/// <param name="startIndex">The index in the buffer to start copying from.</param>
		/// <param name="array">The array to copy into.</param>
		/// <param name="arrayIndex">Index in the array to copy to.</param>
		/// <param name="count">The number of items to copy.</param>
		public void CopyBuffer(int startIndex, T[] array, int arrayIndex, int count) {
			if (arrayIndex + count > array.Length)
				throw new ArgumentOutOfRangeException("count");
			if (startIndex + count > Count)
				throw new ArgumentOutOfRangeException("count");
			if (count > 0)
				this.CopyBufferCore(startIndex, array, arrayIndex, count);
		}

		/// <summary>
		/// Implements the actual copying of items from the buffer into an array.
		/// </summary>
		/// <param name="startIndex">The index in the buffer to start copying from.</param>
		/// <param name="array">The array to copy into.</param>
		/// <param name="arrayIndex">Index in the array to copy to.</param>
		/// <param name="count">The number of items to copy.</param>
		protected abstract void CopyBufferCore(int startIndex, T[] array, int arrayIndex, int count);

		/// <summary>
		/// Gets the item directly before the current positon.
		/// </summary>
		/// <value>The item.</value>
		public virtual T Previous { get { return this[Position - 1]; } }
		/// <summary>
		/// Gets the item directly after the current positon.
		/// </summary>
		/// <value>The item.</value>
		public virtual T Next { get { return this[Position]; } }

		#region IList<T> Members

		/// <summary>
		/// Determines the index of a specific item in the <see cref="T:Liquid.Collections.BufferBase`1"/>.
		/// </summary>
		/// <param name="item">The object to locate in the <see cref="T:Liquid.Collections.BufferBase`1"/>.</param>
		/// <returns>
		/// The index of <paramref name="item"/> if found in the list; otherwise, -1.
		/// </returns>
		public abstract int IndexOf(T item);
		/// <summary>
		/// Gets or sets the item at the specified index.
		/// </summary>
		/// <value></value>
		public abstract T this[int index] { get; set; }

		void IList<T>.Insert(int index, T item) {
			Position = index;
			Insert(item);
		}

		void IList<T>.RemoveAt(int index) {
			Position = index;
			DeleteNext();
		}
		#endregion

		#region ICollection<T> Members

		/// <summary>
		/// Removes all items from the <see cref="T:Liquid.Collections.BufferBase`1"/>.
		/// </summary>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:Liquid.Collections.BufferBase`1"/> is read-only. </exception>
		public abstract void Clear();

		/// <summary>
		/// Determines whether the <see cref="T:Liquid.Collections.BufferBase`1"/> contains a specific value.
		/// </summary>
		/// <param name="item">The object to locate in the <see cref="T:Liquid.Collections.BufferBase`1"/>.</param>
		/// <returns>
		/// true if <paramref name="item"/> is found in the <see cref="T:Liquid.Collections.BufferBase`1"/>; otherwise, false.
		/// </returns>
		public virtual bool Contains(T item) {
			return Enumerable.Contains(this, item);
		}

		/// <summary>
		/// Copies the elements of the <see cref="T:Liquid.Collections.BufferBase`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
		/// </summary>
		/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:Liquid.Collections.BufferBase`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param>
		/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
		/// <exception cref="T:System.ArgumentNullException">
		/// 	<paramref name="array"/> is null.</exception>
		/// <exception cref="T:System.ArgumentOutOfRangeException">
		/// 	<paramref name="arrayIndex"/> is less than 0.</exception>
		/// <exception cref="T:System.ArgumentException">
		/// 	<paramref name="array"/> is multidimensional.-or-<paramref name="arrayIndex"/> is equal to or greater than the length of <paramref name="array"/>.-or-The number of elements in the source <see cref="T:Liquid.Collections.BufferBase`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.-or-Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception>
		public abstract void CopyTo(T[] array, int arrayIndex);

		/// <summary>
		/// Gets the number of elements contained in the <see cref="T:Liquid.Collections.BufferBase`1"/>.
		/// </summary>
		/// <value></value>
		/// <returns>The number of elements contained in the <see cref="T:Liquid.Collections.BufferBase`1"/>.</returns>
		public abstract int Count { get; }

		void ICollection<T>.Add(T item) {
			Position = Count;
			Insert(item);
		}

		bool ICollection<T>.IsReadOnly { get { return false; } }

		bool ICollection<T>.Remove(T item) {
			int index = IndexOf(item);
			if (index == -1) {
				return false;
			} else {
				Position = index;
				DeleteNext();
				return true;
			}
		}

		#endregion

		#region IEnumerable<T> Members

		/// <summary>
		/// Returns an enumerator that iterates through the collection.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
		/// </returns>
		public abstract IEnumerator<T> GetEnumerator();

		#endregion

		#region IEnumerable Members

		IEnumerator IEnumerable.GetEnumerator() {
			return this.GetEnumerator();
		}

		#endregion

		public virtual T[] ToArray() {
			var count = this.Count;
			T[] array = new T[count];
			CopyBuffer(0, array, 0, count);
			return array;
		}
	}
}
